import json
import traceback
from typing import Type, TypeVar

import jwt
import tornado
from pydantic import ValidationError

from common.biz_exception import ParamCheckError
from common.encoder import DefaultEncoder
from common.message import MesCode
from common.pydantic_base_model import BaseModel
from config import logger, BasicConfig
from dao.user_dao import UserDAO

PydanticBaseModel = TypeVar('PydanticBaseModel', bound=BaseModel)


class BasicHandler(tornado.web.RequestHandler):

    def set_default_headers(self):
        # 允许浏览器访问指定的自定义响应头（如 Content-Disposition）
        self.set_header("Access-Control-Expose-Headers", "Content-Disposition")
        # 允许跨域的 Origin 来源
        self.set_header("Access-Control-Allow-Origin", self.request.headers.get('Origin') or "*")
        # 允许跨域时发送凭据（如 Cookie 或 Authorization 头）
        self.set_header("Access-Control-Allow-Credentials", "true")
        # 允许的 HTTP 方法
        self.set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        # 允许的自定义请求头
        self.set_header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")

    def options(self, *args, **kwargs):
        # OPTIONS 请求不需要返回任何内容，只需设置状态码为 200
        self.set_status(204)
        self.finish()

    def get_remote_ip(self):
        return self.request.headers.get("X-Real-IP") or \
            self.request.headers.get("X-Forwarded-For") or \
            self.request.remote_ip

    @property
    async def user(self):
        try:
            token = self.request.headers['Authorization'].split(' ')[1]
            decode_payload = jwt.decode(token, BasicConfig.SECRET_KEY, algorithms=["HS256"])
            username = decode_payload['username']
            user_model = await UserDAO().get_user_by_username(username)
            return user_model
        except jwt.ExpiredSignatureError:
            self.send_response(MesCode.fail, None, 'Token has expired!')
        except jwt.InvalidTokenError:
            self.send_response(MesCode.fail, None, 'Invalid token!')

    @property
    def data(self):
        json_data = {}
        try:
            if self.request.query_arguments:
                # 参数来源于url中,数据结构简单,简单键值对,好解析,一般框架会自己解析
                query_arguments = self.request.query_arguments
                for i in query_arguments.keys():
                    json_data[i] = self.get_argument(i)
            # body请求体格式复杂多样( JSON、表单数据、XML等等),框架通常不会自动解析 JSON 数据。
            if self.request.body_arguments:
                body_arguments = self.request.body_arguments
                for i in body_arguments.keys():
                    json_data[i] = self.get_body_argument(i)
            if self.request.body:
                # json格式
                body_arguments = json.loads(self.request.body.decode("utf-8"))
                json_data.update(body_arguments)
        except:
            # e 用于获取异常的简单描述或信息。它是直接从异常对象中获取的，通常用于打印或记录错误消息。
            # traceback.format_exc() 用于获取完整的堆栈跟踪信息，适用于需要详细的错误上下文（如日志记录、调试信息等）时。
            logger.error(f'parse request params error:{traceback.format_exc()}')

        return json_data

    def write_error(self, status_code, **kwargs):
        exc_cls, exc_instance, trace = kwargs.get("exc_info")

        if isinstance(exc_instance, ParamCheckError):
            self.set_status(200)
            self.send_response(MesCode.fail, None, exc_instance.msg)
            return

        # 只处理500的错误
        if status_code == 500:
            logger.error(traceback.format_exc())
            self.set_status(status_code)
            self.send_response(MesCode.fail, None, '服务器内部错误')

    @staticmethod
    def check_params(cls: Type[PydanticBaseModel], **kwargs):
        """pydantic只对输出做约束,不对输入做校验,因此需要自定义入参校验"""
        try:
            obj = cls(**kwargs)
            return obj
        except ValidationError as e:
            for error in e.errors():
                loc, msg = error['loc'][0], error['msg']
                raise ParamCheckError(loc, msg)

    def send_response(self, code, data, msg):
        data = {'code': code, 'data': data, 'msg': msg}
        # 前端将json字符串解析成js对象使用,后端将json字符串解析为python 的字典对象使用
        self._set_response_data(data)

    def _set_response_data(self, result, encoder=DefaultEncoder):
        try:
            self.finish(json.dumps(result, ensure_ascii=False, cls=encoder))
        except Exception as e:
            self.finish(json.dumps(
                {"code": MesCode.fail, "info": str(e)}))
            traceback.print_exc()
